package cn.wjssl.config;

import cn.wjssl.types.annotations.DCCValue;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.CuratorCache;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * 动态配置工厂
 */
@Slf4j
@Configuration
public class DCCValueBeanFactory implements BeanPostProcessor {

    // 根节点
    private static final String BASE_CONFIG_PATH = "/big-market-dcc";
    // 配置节点, 后面还要加属性
    private static final String BASE_CONFIG_PATH_CONFIG = BASE_CONFIG_PATH + "/config";

    // 用来存储动态变更的属性bean
    private final Map<String, Object> dccObjMap = new HashMap<>();

    // zk客户端, 通过构造器引入, 因为还需要进行一些初始化设置
    private final CuratorFramework zkClient;

    /**
     * 创建根节点, 启动监听器
     * @param zkClient
     * @throws Exception
     */
    public DCCValueBeanFactory(CuratorFramework zkClient) throws Exception {
        this.zkClient = zkClient;

        // 1. 在 ZooKeeper 中创建 /big-market-dcc/config 节点（如果不存在）。这是所有动态配置的根目录。
        if (zkClient.checkExists().forPath(BASE_CONFIG_PATH_CONFIG) == null) {
            zkClient.create().creatingParentsIfNeeded().forPath(BASE_CONFIG_PATH_CONFIG);
            log.info("DCC 节点监听 base node {} not absent create new done!", BASE_CONFIG_PATH_CONFIG);
        }
        // 2. 监听 /big-market-dcc/config 目录下的节点变更事件（如 NODE_CHANGED）, 通过反射更新内存中对应的 Bean 字段值。
        CuratorCache curatorCache = CuratorCache.build(zkClient, BASE_CONFIG_PATH_CONFIG);
        curatorCache.start();
        curatorCache.listenable().addListener((type, oldData, data) -> {
            switch (type) {
                case NODE_CHANGED:
                    // 获取该属性的节点
                    String dccValuePath = data.getPath();
                    // 从java程序内存的map中获取该Bean对象
                    Object objBean = dccObjMap.get(dccValuePath);
                    // 如果没有bean对象, 那直接返回
                    if (null == objBean) return;
                    /**
                     * 通过反射更新内存中对应的 Bean 字段值。
                     * getDeclaredField 方法用于获取指定类中声明的所有字段，包括私有字段、受保护字段和公共字段。
                     * getField 方法用于获取指定类中的公共字段，即只能获取到公共访问修饰符（public）的字段。
                     */
                    try {
                        // 这里相当于 /big-market-dcc/config/xxx 然后从最后一个 / 后的第一个字符开始截取, 相当于获得xxx
                        Field field = objBean.getClass().getDeclaredField(dccValuePath.substring(dccValuePath.lastIndexOf("/") + 1));
                        field.setAccessible(true);
                        // 给字段设置值
                        field.set(objBean, new String(data.getData()));
                        field.setAccessible(false);
                    }catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                    break;
                default:
                    break;
            }
        });
    }

    /**
     * 在 Bean 初始化后，检查 @DCCValue 注解，并把字段值 从 Zookeeper读取 或 创建默认值。
     * BeanPostProcessor 会在Spring初始化每一个 Bean 之后，自动调用 postProcessAfterInitialization()，并将所有 Bean 传入该方法。
     *
     * 1. 在 Spring 初始化每个 Bean 后，检查其字段是否标记了 @DCCValue 注解。
     * 2. 从 @DCCValue 注解中提取配置键（key）和默认值（defaultValue），格式为 key:defaultValue（例如 degradeSwitch:open）。
     * 3. 同步 ZooKeeper 配置
     *      如果 ZooKeeper 中不存在对应节点（如 /big-market-dcc/config/degradeSwitch），则创建该节点，并将默认值写入 Bean 字段。
     *      如果节点已存在，则从 ZooKeeper 读取当前值并设置到 Bean 字段。
     * 4. 将当前 Bean 存入 dccObjMap，键为 ZooKeeper 节点路径（如 /big-market-dcc/config/degradeSwitch）。后续监听到节点变更时，可通过该 Map 快速找到需要更新的 Bean。
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> targetbeanClass = bean.getClass();   // 获取bean对象的类
        Object targetBeanObject = bean;

        if (AopUtils.isAopProxy(bean)) {    // 判断注解是不是在AOP代理类里
            targetbeanClass = AopUtils.getTargetClass(bean);
            targetBeanObject = AopProxyUtils.getSingletonTarget(bean);
        }

        Field[] fields = targetbeanClass.getDeclaredFields();     // 获取类中所有的属性字段
        for (Field field : fields) {    // 遍历所有的字段
            if (!field.isAnnotationPresent(DCCValue.class)) {   // 如果该字段没有我自定义的注解DCCValue, 那就继续看下一个字段
                continue;
            }

            DCCValue dccValue = field.getAnnotation(DCCValue.class);    // 如果有, 拿到我字段上的自定义注解
            String value = dccValue.value();    // 拿到注解中的值
            if (StringUtils.isBlank(value)) {   // 如果值是空的就报异常
                throw new RuntimeException(field.getName() + " @DCCValue is not config value config case 「isSwitch/isSwitch:1」");
            }
            String[] splits = value.split(":"); // 格式应该是 degradeSwitch:open
            String key = splits[0]; // 前面的是要动态配置的字段
            String defaultValue = splits.length == 2 ? splits[1] : null;    // 后面是值

            String keyPath = BASE_CONFIG_PATH_CONFIG + "/" + key;
            try {
                // 判断有没有该字段的节点, 没有就创建
                if (zkClient.checkExists().forPath(keyPath) == null) {
                    zkClient.create().creatingParentsIfNeeded().forPath(keyPath);   // 创建节点
                    if (StringUtils.isNotBlank(defaultValue)) {     // 保持默认值
                        field.setAccessible(true);  // 设置字段属性可修改
                        field.set(targetBeanObject, defaultValue);
                        field.setAccessible(false); // 设置字段属性私有, 不可修改
                    }
                    log.info("DCC 节点监听 创建节点 {}", keyPath);
                } else {    // 如果本来就有这个字段的节点了, 那就获取 zookeeper 中, 字段当前的值, 修改到当前应用中
                    String configValue = new String(zkClient.getData().forPath(keyPath));
                    if (StringUtils.isNotBlank(configValue)) {
                        field.setAccessible(true);
                        field.set(targetBeanObject, configValue);
                        field.setAccessible(false);
                        log.info("DCC 节点监听 设置配置 {} {} {}", keyPath, field.getName(), configValue);
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            dccObjMap.put(keyPath, targetBeanObject);   // 更新内存中的bean对象, 键为 ZooKeeper 节点路径（如 /big-market-dcc/config/degradeSwitch）
        }
        return bean;
    }
}
